package com.cloudfun.campusshare.controller;

import cn.hutool.core.bean.BeanUtil;
import com.cloudfun.campusshare.common.constant.Code;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.KjtNotifyDTO;
import com.cloudfun.campusshare.common.model.dto.kjt.KjtNotifyBaseDTO;
import com.cloudfun.campusshare.service.KjtNotifyService;
import com.cloudfun.campusshare.service.handler.KjtNotifyHandler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kjtpay.gateway.common.domain.VerifyResult;
import com.kjtpay.gateway.common.util.security.SecurityService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 商户需要对通知进行签名验证，并在成功接收到通知内容后返回小写的“success”字符串。
 * 否则，认为是通知失败，会重发通知。
 * 通知最多重复发送7次，频率依次为自上次发送后的时间间隔：[2, 10, 10, 60, 120, 360, 900]，单位分钟。
 * <p>
 * 注意:
 * (1)不能有页面重定向功能存在，另外务必要判断被重复通知的可能。
 * (2)如快捷通巡检发现商户在1个月内对快捷通发出的异步通知从未返回过“success”字符串，快捷通将保留取消对该商户发送异步通知功能的权利。
 * Created by Huqin on 2020/8/5
 */
@Slf4j
@RestController
@RequestMapping("/v1/kjt/notify")
public class KjtNotifyController {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private KjtNotifyService kjtNotifyService;

    @Autowired
    private SecurityService securityService;

    @Autowired
    private List<KjtNotifyHandler<? extends KjtNotifyBaseDTO>> notifyHandlers;

    @PostConstruct
    public void init() {
        Map<String, List<KjtNotifyHandler<? extends KjtNotifyBaseDTO>>> map = notifyHandlers.stream().collect(Collectors.groupingBy(KjtNotifyHandler::notifyType));
        for (Map.Entry<String, List<KjtNotifyHandler<? extends KjtNotifyBaseDTO>>> typeHandlersEntry : map.entrySet()) {
            if (typeHandlersEntry.getValue().size() > 1) {
                throw new RuntimeException("重复的快捷通通知处理器：" + typeHandlersEntry.getValue());
            }
        }
    }

    @PostMapping
    public String notify(@RequestBody String kvString) {
        log.info("收到快捷通子商户通知：{}", kvString);
        String notifyJsonString = this.writeJson(this.readKvString(kvString));
        this.verifySign(notifyJsonString);
        KjtNotifyBaseDTO notifyBaseDTO = this.readJson(notifyJsonString, KjtNotifyBaseDTO.class);
        this.saveNotify(notifyBaseDTO, notifyJsonString);
        boolean handleRes = false;
        boolean handled = false;
        for (KjtNotifyHandler<? extends KjtNotifyBaseDTO> notifyHandler : notifyHandlers) {
            if (StringUtils.equals(notifyHandler.notifyType(), notifyBaseDTO.getNotifyType())) {
                KjtNotifyBaseDTO notifyDTO = this.readJson(notifyJsonString, notifyHandler.notifyMessageType());
                handleRes = notifyHandler.handle(notifyDTO);
                handled = true;
            }
        }
        if (!handled) {
            log.error("快捷通子商户通知无法处理。notifyId: {}", notifyBaseDTO);
        }
        if (handleRes) {
            return "success";
        } else {
            return "fail";
        }
    }

    /**
     * 验签
     *
     * @param notifyJsonString
     */
    private void verifySign(String notifyJsonString) {
        TreeMap treeMap = this.readJson(notifyJsonString, TreeMap.class);
        String sign = (String) treeMap.remove("sign");
        treeMap.remove("sign_type");
        VerifyResult verifyResult = securityService.verify(treeMap, sign, "UTF-8");
        if (!verifyResult.isSuccess()) {
            log.info("快捷通通知，notifyType: {}, notifyId: {}, 验签失败，通知数据：{}, 验签结果: {}", treeMap.get("notify_type"),
                    treeMap.get("notify_id"), notifyJsonString, verifyResult);
            throw new BusinessException(Code.ERROR, "验签失败");
        }
    }

    /**
     * 保存
     *
     * @param notifyBaseDTO
     * @param notifyJsonString
     */
    private void saveNotify(KjtNotifyBaseDTO notifyBaseDTO, String notifyJsonString) {
        KjtNotifyDTO notifyDTO = new KjtNotifyDTO();
        BeanUtil.copyProperties(notifyBaseDTO, notifyDTO, "notifyTime");
        notifyDTO.setNotifyTime(LocalDateTime.parse(notifyBaseDTO.getNotifyTime(), KjtNotifyBaseDTO.NOTIFY_TIME_FORMATTER));
        notifyDTO.setContent(notifyJsonString);
        kjtNotifyService.create(notifyDTO);
    }

    /**
     * 读取key=value&key=value格式的字符串
     *
     * @param kvString
     * @return
     */
    private Map<String, String> readKvString(String kvString) {
        return Arrays.stream(kvString.split("&"))
                .map(kv -> kv.split("="))
                .collect(Collectors.toMap(a -> a[0], a -> valueReader().apply(a)));
    }

    private static Function<String[], String> valueReader() {
        return arr -> {
            try {
                return URLDecoder.decode(arr[1], "UTF-8");
            } catch (UnsupportedEncodingException e) {
                log.error("urlEncode异常: ", e);
                throw new BusinessException(Code.ERROR, "处理快捷通参数失败，url encode异常");
            }
        };
    }

    private String writeJson(Object obj) {
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            log.error("将：{}序列化json时异常：", obj, e);
            throw new BusinessException(Code.PARAMETER_ERROR);
        }
    }

    private <T> T readJson(String jsonStr, Class<T> valueType) {
        try {
            return objectMapper.readValue(jsonStr, valueType);
        } catch (IOException e) {
            log.error("反序列化json: {}时异常：", jsonStr, e);
            throw new BusinessException(Code.PARAMETER_ERROR);
        }
    }

}
